/*
Copyright 2016 S7connector members (github.com/s7connector)

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.mt.roll.opcua.s7.my.s7connector.impl.nodave;

import com.mt.roll.opcua.s7.my.s7connector.api.DaveArea;
import com.mt.roll.opcua.s7.my.s7connector.type.PlcVar;
import com.mt.roll.opcua.s7.my.s7connector.type.TransportSize;

import java.util.concurrent.Semaphore;

/**
 * This class comprises the variables and methods common to connections to an S7
 * PLC regardless of the type of transport.
 *
 * @author Thomas Hergenhahn
 */
public abstract class S7Connection {
	static int tmo_normal = 150;
	int answLen; // length of last message
	/**
	 * position in result data, incremented when variables are extracted without
	 * position
	 */
	int dataPointer;
	PLCinterface iface; // pointer to used interface
	public int maxPDUlength; //第二次握手里 的 PDU Length(2个字节)
	public byte messageNumber = 0;

	//msgIn 和 msgOut 同理
	public byte[] msgIn;
	//msgOut 确实是当前TcpConnection共用的，但不影响每次write，因为每次发送都会修改msgOut
	public byte[] msgOut;

	public int packetNumber = 0; // packetNumber in transport layer
	public int PDUstartIn;
	public int PDUstartOut;
	PDU rcvdPDU;
	public Semaphore semaphore;

	/**
	 * absolute begin of result data
	 */
	int udata;

	public S7Connection(final PLCinterface ifa) {
		this.iface = ifa;
		this.msgIn = new byte[Nodave.MAX_RAW_LEN];
		this.msgOut = new byte[Nodave.MAX_RAW_LEN];
		this.PDUstartIn = 0;
		this.PDUstartOut = 0;
		this.semaphore = new Semaphore(1);
	}

	abstract public int exchange(PDU p1);

	// int numResults;
	/*
	 * class Result { int error; byte[] data; }
	 */
	/*
	 * Read a predefined set of values from the PLC. Return ok or an error state
	 * If a buffer pointer is provided, data will be copied into this buffer. If
	 * it's NULL you can get your data from the resultPointer in daveConnection
	 * long as you do not send further requests.
	 */
	public ResultSet execReadRequest(final PDU p) {
		PDU p2;
		int errorState;
		errorState = this.exchange(p);

		p2 = new PDU(this.msgIn, this.PDUstartIn);
		p2.setupReceivedPDU();
		/*
		 * if (p2.udlen == 0) { dataPointer = 0; answLen = 0; return
		 * Nodave.RESULT_CPU_RETURNED_NO_DATA; }
		 */
		final ResultSet rs = new ResultSet();
		if (p2.mem[p2.param + 0] == PDU.FUNC_READ) {
			int numResults = p2.mem[p2.param + 1];
			// System.out.println("Results " + numResults);
			rs.results = new Result[numResults];
			int pos = p2.data;
			for (int i = 0; i < numResults; i++) {
				final Result r = new Result();
				r.error = Nodave.USByte(p2.mem, pos);
				if (r.error == 255) {

					final int type = Nodave.USByte(p2.mem, pos + 1);
					int len = Nodave.USBEWord(p2.mem, pos + 2);
					r.error = 0;
					// System.out.println("Raw length " + len);
					if (type == 4) {
						len /= 8;
					} else if (type == 3) {
						; // length is ok
					}

					// System.out.println("Byte length " + len);
					// r.data = new byte[len];

					// System.arraycopy(p2.mem, pos + 4, r.data, 0, len);
					// Nodave.dump("Result " + i + ":", r.data, 0, len);
					r.bufferStart = pos + 4;
					pos += len;
					if ((len % 2) == 1) {
						pos++;
					}
				} else {
					System.out.println("Error " + r.error);
				}
				pos += 4;
				rs.results[i] = r;
			}
			numResults = p2.mem[p2.param + 1];
			rs.setNumResults(numResults);
			this.dataPointer = p2.udata;
			this.answLen = p2.udlen;
			// }
		} else {
			errorState |= 2048;
		}
		this.semaphore.release();
		rs.setErrorState(errorState);
		return rs;
	}

	public int getBYTE() {
		this.dataPointer += 1;
		return Nodave.SByte(this.msgIn, this.dataPointer - 1);
	}

	public int getBYTE(final int pos) {
		return Nodave.SByte(this.msgIn, this.udata + pos);
	}

	public int getCHAR() {
		this.dataPointer += 1;
		return Nodave.SByte(this.msgIn, this.dataPointer - 1);
	}

	public int getCHAR(final int pos) {
		return Nodave.SByte(this.msgIn, this.udata + pos);
	}

	/**
	 * get an signed 32bit value from the current position in result bytes
	 */
	public long getDINT() {
		this.dataPointer += 4;
		return Nodave.SBELong(this.msgIn, this.dataPointer - 4);
	}

	/**
	 * get an signed 32bit value from the specified position in result bytes
	 */
	public long getDINT(final int pos) {
		return Nodave.SBELong(this.msgIn, this.udata + pos);
	}

	/**
	 * get an unsigned 32bit value from the specified position in result bytes
	 */
	public long getDWORD(final int pos) {
		// System.out.println("getDWORD pos " + pos);
		return Nodave.USBELong(this.msgIn, this.udata + pos);
	}

	/**
	 * get a float value from the current position in result bytes
	 */
	public float getFloat() {
		this.dataPointer += 4;
		return Nodave.BEFloat(this.msgIn, this.dataPointer - 4);
	}

	/*
	 * The following methods are here to give Siemens users their usual data
	 * types:
	 */
	/**
	 * get a float value from the specified position in result bytes
	 */
	public float getFloat(final int pos) {
		// System.out.println("getFloat pos " + pos);
		return Nodave.BEFloat(this.msgIn, this.udata + pos);
	}

	public int getINT() {
		this.dataPointer += 2;
		return Nodave.SBEWord(this.msgIn, this.dataPointer - 2);
	}

	public int getINT(final int pos) {
		return Nodave.SBEWord(this.msgIn, this.udata + pos);
	}

	public int getPPIresponse() {
		return 0;
	}

	/*
	 * public void sendYOURTURN() { }
	 */
	public int getResponse() {
		return 0;
	}

	public int getS16(final int pos) {
		return Nodave.SBEWord(this.msgIn, this.udata + pos);
	}

	public long getS32(final int pos) {
		return Nodave.SBELong(this.msgIn, this.udata + pos);
	}

	public int getS8(final int pos) {
		return Nodave.SByte(this.msgIn, this.udata + pos);
	}

	/**
	 * get an unsigned 32bit value from the current position in result bytes
	 */
	public long getU32() {
		this.dataPointer += 4;
		return Nodave.USBELong(this.msgIn, this.dataPointer - 4);
	}

	public int getUS16(final int pos) {
		return Nodave.USBEWord(this.msgIn, this.udata + pos);
	}

	public long getUS32(final int pos) {
		return Nodave.USBELong(this.msgIn, this.udata + pos);
	}

	public int getUS8(final int pos) {
		return Nodave.USByte(this.msgIn, this.udata + pos);
	}

	/**
	 * get an unsigned 16bit value from the current position in result bytes
	 */
	public int getWORD() {
		this.dataPointer += 2;
		return Nodave.USBEWord(this.msgIn, this.dataPointer - 2);
	}

	/**
	 * get an unsigned 16bit value from the specified position in result bytes
	 */
	public int getWORD(final int pos) {
		return Nodave.USBEWord(this.msgIn, this.udata + pos);
	}

	/*
	 * build the PDU for a PDU length negotiation
	 * 构建第二次握手
	 */
	public int negPDUlengthRequest() {
		int res;
		final PDU p = new PDU(this.msgOut, this.PDUstartOut);
		//S7-Param  构造
		final byte pa[] = {
				(byte) 0xF0,   //Function：step Communication
				0,			   //Reserved
				0x00,		   //Max AmQ (parallel jobs with ack) calling，也就是说这个connection每次只能调用1次  ;;2个字节
				0x01,
				0x00,		   //Max AmQ (parallel jobs with ack) called，也就是说这个connection每次只能被调用1次  ;; 2个字节
				0x01,
				0x03,		   //PDU Length (2个字节)  //这里是960 ，，一般是240/480/960   主要是和CPU型号有关的。
				(byte) 0xC0,
		};
		//初始化  S7 Header长度
		p.initHeader(1);
		//加载 param 参数到 请求里面去
		p.addParam(pa);
		//构建第二次握手cotp 部分
		res = this.exchange(p);
		if (res != 0) {
			return res;
		}

		//构建第二次握手 S7COMM  的接收容器。
		final PDU p2 = new PDU(this.msgIn, this.PDUstartIn);
		res = p2.setupReceivedPDU();
		if (res != 0) {
			return res;
		}
		this.maxPDUlength = Nodave.USBEWord(this.msgIn, p2.param  +6);
		return res;
	}

	//reutrn
	//      0  代表成功
	//      1  代表失败
	// 异常 就会抛出异常
	public int readBytes(final DaveArea area, final int DBnum, final int start, final int len, final byte[] buffer) {
		int res = 0;
		try {
			//信号量 ，加锁，，防止多处并发访问
			this.semaphore.acquire();
		} catch (final InterruptedException e) {
			e.printStackTrace();
		}

		//构建 request PDU
		final PDU p1 = new PDU(this.msgOut, this.PDUstartOut);
		p1.initReadRequest();
		p1.addVarToReadRequest(area, DBnum, start, len);
		//发送read var request
		res = this.exchange(p1);
		if (res != Nodave.RESULT_OK) {
			this.semaphore.release();
			return res;
		}

		//构建 response PDU  ，，  注意这里的msgIn 就是接收到的 plc=》上位机 的字节流
		final PDU p2 = new PDU(this.msgIn, this.PDUstartIn);
		// 这里配置received PUD参数。
		res = p2.setupReceivedPDU();
		if (res != Nodave.RESULT_OK) {
			this.semaphore.release();
			return res;
		}

		res = p2.testReadResult();
		if (res != Nodave.RESULT_OK) {
			this.semaphore.release();
			return res;
		}
		if (p2.udlen == 0) {
			this.semaphore.release();
			return Nodave.RESULT_CPU_RETURNED_NO_DATA;
		}
		/*
		 * copy to user buffer and setup internal buffer pointers:
		 * 判断外部确实 外部是否真实传 buffer 这个字节容器进来。
		 */
		if (buffer != null) {
			System.arraycopy(p2.mem, p2.udata, buffer, 0, p2.udlen);
		}

		this.dataPointer = p2.udata;
		this.udata = p2.udata;
		this.answLen = p2.udlen;
		this.semaphore.release();
		return res;
	}

	//reutrn
	//      0  代表成功
	//      1  代表失败
	// 异常 就会抛出异常
	//start == byteAddress ；；  bitStart ==
	public int readBytes(final DaveArea area, final int DBnum, final int byteAddress, final int bitAddress, final int len, final byte[] buffer, TransportSize transportSize) {
		int res = 0;
		try {
			//信号量 ，加锁，，防止多处并发访问
			this.semaphore.acquire();
		} catch (final InterruptedException e) {
			e.printStackTrace();
		}

		//构建 request PDU
		final PDU p1 = new PDU(this.msgOut, this.PDUstartOut);
		p1.initReadRequest();
		p1.addVarToReadRequest(area, DBnum, byteAddress,bitAddress, len,transportSize);
		//发送read var request
		res = this.exchange(p1);
		if (res != Nodave.RESULT_OK) {
			this.semaphore.release();
			return res;
		}

		//构建 response PDU  ，，  注意这里的msgIn 就是接收到的 plc=》上位机 的字节流
		final PDU p2 = new PDU(this.msgIn, this.PDUstartIn);
		// 这里配置received PUD参数。
		res = p2.setupReceivedPDU();
		if (res != Nodave.RESULT_OK) {
			this.semaphore.release();
			return res;
		}

		res = p2.testReadResult();
		if (res != Nodave.RESULT_OK) {
			this.semaphore.release();
			return res;
		}
		if (p2.udlen == 0) {
			this.semaphore.release();
			return Nodave.RESULT_CPU_RETURNED_NO_DATA;
		}
		/*
		 * copy to user buffer and setup internal buffer pointers:
		 * 判断外部确实 外部是否真实传 buffer 这个字节容器进来。
		 */
		if (buffer != null) {
			System.arraycopy(p2.mem, p2.udata, buffer, 0, p2.udlen);
		}

		this.dataPointer = p2.udata;
		this.udata = p2.udata;
		this.answLen = p2.udlen;
		this.semaphore.release();
		return res;
	}

	public int sendMsg(final PDU p) {
		return 0;
	}

	public void sendRequestData(final int alt) {
	}

	public int useResult(final ResultSet rs, final int number) {
		System.out.println("rs.getNumResults: " + rs.getNumResults() + " number: " + number);
		if (rs.getNumResults() > number) {
			this.dataPointer = rs.results[number].bufferStart;
			return 0;
			// udata=rs.results[number].bufferStart;
		}
		return -33;
	};

	/*
	 * Write len bytes to PLC memory area "area", data block DBnum.
	 */
	public int writeBytes(final DaveArea area, final int DBnum, final int start, final int len, final byte[] buffer) {
		int errorState = 0;
		this.semaphore.release();
		final PDU p1 = new PDU(this.msgOut, this.PDUstartOut);

		// p1.constructWriteRequest(area, DBnum, start, len, buffer);
		p1.prepareWriteRequest();
		p1.addVarToWriteRequest(area, DBnum, start, len, buffer);

		errorState = this.exchange(p1);

		if (errorState == 0) {
			final PDU p2 = new PDU(this.msgIn, this.PDUstartIn);
			p2.setupReceivedPDU();

			if (p2.mem[p2.param + 0] == PDU.FUNC_WRITE) {
				if (p2.mem[p2.data + 0] == (byte) 0xFF) {
					this.semaphore.release();
					return 0;
				}
			} else {
				errorState |= 4096;
			}
		}
		this.semaphore.release();
		return errorState;
	}


	public int writeBytes(final DaveArea area, final int DBnum, final int byteOffset, final int bitOffset, final int len, final byte[] buffer,final PlcVar var) {
		int errorState = 0;
		this.semaphore.release();
		final PDU p1 = new PDU(this.msgOut, this.PDUstartOut);

		// p1.constructWriteRequest(area, DBnum, start, len, buffer);
		p1.prepareWriteRequest();
		p1.addVarToWriteRequest(area, DBnum, byteOffset,bitOffset, len, buffer,var);

		errorState = this.exchange(p1);

		if (errorState == 0) {
			final PDU p2 = new PDU(this.msgIn, this.PDUstartIn);
			p2.setupReceivedPDU();

			if (p2.mem[p2.param + 0] == PDU.FUNC_WRITE) {
				if (p2.mem[p2.data + 0] == (byte) 0xFF) {
					this.semaphore.release();
					return 0;
				}
			} else {
				errorState |= 4096;
			}
		}
		this.semaphore.release();
		return errorState;
	}

}
